function TravelStep(travelStepDto) {
    this._departureLocation = travelStepDto.departureLocation || null;
    this._departureDateTime = (typeof (travelStepDto.departureDateTime) === "string" ? Date.parse(travelStepDto.departureDateTime) : travelStepDto.departureDateTime);
    this._departureActivity = travelStepDto.departureActivity;
    this._arrivalLocation = travelStepDto.arrivalLocation || null;
    this._arrivalDateTime = (typeof (travelStepDto.arrivalDateTime) === "string" ? Date.parse(travelStepDto.arrivalDateTime) : travelStepDto.arrivalDateTime);
    this._arrivalActivity = travelStepDto.arrivalActivity;
    this._tid = travelStepDto.tid || null;
    this._error = new Error();
};

TravelStep.prototype = {
    getDateRange: function() {
        return new DateRange(this.getDepartureDateTime(), this.getArrivalDateTime());
    },
    containsDay: function(date) {
        return this.getDateRange().containsDate(date);
    },

    containsDayInMonth: function(year, month) {
        var travelDateRange = this.getDateRange();
        var yearMonth = new YearMonth(year, month);
        return travelDateRange.hasOverlapWith(yearMonth.dateRangeOfMonth());
    },

    getDepartureLocation: function() {
        return this._departureLocation;
    },

    setDepartureLocation: function(location) {
        this._departureLocation = location;
    },

    getArrivalLocation: function() {
        return this._arrivalLocation;
    },

    setArrivalLocation: function(location) {
        this._arrivalLocation = location;
    },

    getDepartureDateTime: function() {
        return this._departureDateTime;
    },

    setDepartureDateTime: function(dateTime) {
        this._departureDateTime = dateTime;
    },

    getArrivalDateTime: function() {
        return this._arrivalDateTime;
    },
    setArrivalDateTime: function(dateTime) {
        this._arrivalDateTime = dateTime;
    },
    getDepartureActivity: function() {
        return this._departureActivity;
    },
    setDepartureActivity: function(activity) {
        this._departureActivity = activity;
    },

    getArrivalActivity: function() {
        return this._arrivalActivity;
    },

    setArrivalActivity: function(activity) {
        this._arrivalActivity = activity;
    },

    getDepartureDate: function() {
        return this._departureDateTime.clone().clearTime();
    },

    getArrivalDate: function() {
        return this._arrivalDateTime.clone().clearTime();
    },
    getId: function() {
        return this._tid;
    },
    setId: function(guid) {
        this._tid = guid;
    },

    toDto: function() {
        return {
            "departureLocation": this._departureLocation,
            "departureDateTime": this._departureDateTime.toString(CalendarConstants.JSONDateTimeFormat),
            "departureActivity": this._departureActivity,
            "arrivalLocation": this._arrivalLocation,
            "arrivalDateTime": this._arrivalDateTime.toString(CalendarConstants.JSONDateTimeFormat),
            "arrivalActivity": this._arrivalActivity,
            "tid": this._tid
        };
    },
    
    hasOverlapWith : function(other) {
        return this.getDateRange().hasOverlapWith(other.getDateRange());        
    },
    isConflictWith: function(other) {
        if (!this.hasOverlapWith(other)) {
            return false;
        }

        //if time ranges have overlap, and no time gain case, conflict happens
        if (!this.hasTimegain() && !other.hasTimegain()) {
            return true;
        }

        //if the departure location is no the same as the other's arrival location, conflict happens
        if (!this._departureLocation.equals(other._arrivalLocation)
                && !this._arrivalLocation.equals(other._departureLocation)) {
            return true;
        }

        if (TravelStep.isValidConnectingTravel(this, other) ||
            TravelStep.isValidConnectingTravel(other, this)) {
            return false;
        }
        if (TravelStep.isValidRoundTravel(this, other) || TravelStep.isValidRoundTravel(other, this)) {
            return false;
        }
        return true;
    },

    isDateValid: function() {
        return this._arrivalDateTime != null && this._departureDateTime != null;
    },

    isValid: function(index) {
        var arrivalViewIndex = this.arrivalViewIndex || index + 1;
        var departureViewIndex = this.departureViewIndex || index;
        
        this._error = new Error();
        this._checkDateTimeAreValid(departureViewIndex, arrivalViewIndex);
        this._checkLocationNotSame(departureViewIndex, arrivalViewIndex);
        this._checkLocationsAreValid(departureViewIndex, arrivalViewIndex);
        return !this._error.hasError();
    },

    _checkDateTimeAreValid: function(departureViewIndex, arrivalViewIndex) {
        if (this._arrivalDateTime == null) {
            this._error.addError(i18n("MSG_TRAVEL_DATE_REQUIRED"), DayDetailDialogViewMap.arrivalDate + arrivalViewIndex);
        }

        if (this._departureDateTime == null) {
            this._error.addError(i18n("MSG_TRAVEL_DATE_REQUIRED"), DayDetailDialogViewMap.departureDate + departureViewIndex);
        }
        if (this.isDateValid()) {
            if (this._arrivalDateTime < this._departureDateTime.clone().addDays(-1)) {
                this._error.addError(i18n("MSG_ARRIVAl_DATETIME_EARLIER_THAN_DEPARTURE_DATETIME"), DayDetailDialogViewMap.departureDate + departureViewIndex);
                this._error.addError(i18n("MSG_ARRIVAl_DATETIME_EARLIER_THAN_DEPARTURE_DATETIME"), DayDetailDialogViewMap.departureTime + departureViewIndex);
                this._error.addError(i18n("MSG_ARRIVAl_DATETIME_EARLIER_THAN_DEPARTURE_DATETIME"), DayDetailDialogViewMap.arrivalDate + arrivalViewIndex);
                this._error.addError(i18n("MSG_ARRIVAl_DATETIME_EARLIER_THAN_DEPARTURE_DATETIME"), DayDetailDialogViewMap.arrivalTime + arrivalViewIndex);
            }
        }
    },
    _checkLocationNotSame: function(departureViewIndex, arrivalViewIndex) {
        if (this._departureLocation && this._departureLocation.equals(this._arrivalLocation)) {
            this._error.addError(i18n("MSG_ARRIVAL_LOCATION_CANNOT_SAME_WITH_DEPARTURE_LOCATION"), DayDetailDialogViewMap.location + departureViewIndex);
            this._error.addError(i18n("MSG_ARRIVAL_LOCATION_CANNOT_SAME_WITH_DEPARTURE_LOCATION"), DayDetailDialogViewMap.location + arrivalViewIndex);
        }
    },
    _checkLocationsAreValid: function(departureViewIndex, arrivalViewIndex) {
        if (!Location.isValidLocation(this._departureLocation)) {
            this._error.addError(i18n("MSG_LOCATION_INVALID"), DayDetailDialogViewMap.location + departureViewIndex);
        }

        if (!Location.isValidLocation(this._arrivalLocation)) {
            this._error.addError(i18n("MSG_LOCATION_INVALID"), DayDetailDialogViewMap.location + arrivalViewIndex);
        }
    },

    getError: function() {
        return this._error;
    },

    hasTimegain: function() {
        return this._departureDateTime > this._arrivalDateTime;
    },

    hasCrossDayTimegain: function() {
        return this.getDepartureDate() > this.getArrivalDate();
    },

    toString: function() {
        return this._departureLocation.fullname + " " +
               this._departureDateTime.toString(DefaultSetting.dateFormat() + " " + DefaultSetting.timeFormat()) +
               ", " +
               this._arrivalLocation.fullname + " " +
               this._arrivalDateTime.toString(DefaultSetting.dateFormat() + " " + DefaultSetting.timeFormat());
    },

    equals: function(other) {
        if (!other) {
            return false;
        }
        if (this.getId() != null && other.getId() != null) {
            return this.getId() == other.getId();
        }
        if (this.getId() == null && other.getId() == null) {
            return this.getDepartureLocation().equals(other.getDepartureLocation())
                    && this.getDepartureDateTime().equals(other.getDepartureDateTime())
                    && this.getArrivalDateTime().equals(other.getArrivalDateTime())
                    && this.getArrivalLocation().equals(other.getArrivalLocation());
        }
        return false;
    },

    isSame: function(other) {
        return this.getId() !== null && other.getId() !== null && this.getId() === other.getId();
    },
    
    isConnectAfter: function(previousStep){
        return this.getDepartureLocation().equals(previousStep.getArrivalLocation()) &&
               previousStep.getArrivalDateTime() < this.getDepartureDateTime();
    }

};

TravelStep.getUniqueTravelStepArray = function(travelStepsArray) {
    var tokens = {};
    var uniqueTravelStepArray = [];
    travelStepsArray.foreach(function(item) {
        if (!tokens[item.getId()]) {
            uniqueTravelStepArray.push(item);
            tokens[item.getId()] = item;
        }
    });

    return uniqueTravelStepArray;
};
TravelStep.isValidConnectingTravel = function(stepArrive, stepDepart) {
    return stepDepart.getDepartureLocation().equals(stepArrive.getArrivalLocation()) &&
           !stepDepart.getArrivalLocation().equals(stepArrive.getDepartureLocation()) &&
           stepDepart.getDepartureDateTime() >= stepArrive.getArrivalDateTime();
};

TravelStep.isValidRoundTravel = function(forwardStep, backwardStep) {
    return backwardStep.getDepartureLocation().equals(forwardStep.getArrivalLocation()) &&
           backwardStep.getArrivalLocation().equals(forwardStep.getDepartureLocation()) &&
           backwardStep.getDepartureDateTime() >= forwardStep.getArrivalDateTime() &&
           backwardStep.getArrivalDateTime() > forwardStep.getDepartureDateTime();
};
TravelStep.sortedTravelSteps = function(travelSteps) {
    if (travelSteps.length == 0) {
        return travelSteps;
    }
    return travelSteps.sort(function(step1, step2) {
        if (!step1.hasOverlapWith(step2)) {
            return step1.getDepartureDateTime() > step2.getDepartureDateTime() ? 1 : -1;
        }
        if (!step1.hasTimegain() && !step2.hasTimegain()) {
            return step1.getDepartureDateTime() - step2.getDepartureDateTime();
        } else {
            if (TravelStep.isValidConnectingTravel(step1, step2)) {
                return -1;
            }
            if (TravelStep.isValidConnectingTravel(step2, step1)) {
                return 1;
            }
            if (TravelStep.isValidRoundTravel(step1, step2)) {
                return -1;
            }
            if (TravelStep.isValidRoundTravel(step2, step1)) {
                return 1;
            }
        }
        //should not come to here. here means the travel steps conflict with each other.
        // return 1 to give user a chance to erase the travel step
        return 1;
    });
};

TravelStep.removeTravelStep = function(stepToBeRemoved, sourceTravelSteps) {
    if (stepToBeRemoved.getId() === null) {
        return sourceTravelSteps;
    }
    return sourceTravelSteps.where(function(step) {
        return !step.isSame(stepToBeRemoved);
    });
};

TravelStep.getDateRange = function(travelSteps) {
    if (travelSteps.length === 0) {
        return null;
    }
    var dateRanges = travelSteps.select(function(travelStep){
        return travelStep.getDateRange().clearTime();
    });
    
    return dateRanges.sum(null, function(dateRange, sum){
        return sum? sum.mergeWith(dateRange) : dateRange;
    });    
};
